package com.wechat.websocket;


import com.alibaba.fastjson.JSON;
import com.wechat.entity.dto.MessageSendDto;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/*
 * @Description: 消息处理（消息中转，集群部署），跨服务器之间传递消息
 */
@Component("messageHandler")
public class MessageHandler<T> {

    private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class);

    private static final String MESSAGE_TOPIC = "message.topic";// 消息主题，全局唯一

    @Resource
    private RedissonClient redissonClient;//Redisson 的核心入口，提供分布式对象（如 RTopic、RMap）的访问

    @Resource
    private ChannelContextUtils channelContextUtils;

    //   服务A发送消息 → Redis发布 → 所有服务实例接收 → 各自转发给本地WebSocket客户端
    //1.消息产生 → 调用 sendMessage(MessageSendDto)
    //2.Redis发布 → 消息被发布到 message.topic
    //3.集群广播 → 所有服务实例都接收到该消息
    //4.本地转发 → 每个实例通过 channelContextUtils.sendMessage 将消息发送给本地连接的客户端
    //5.客户端接收 → WebSocket 客户端收到实时消息

    //根据contactId确定消息接受的用户
    //根据USER_CONTEXT_MAP 或 GROUP_CONTEXT_MAP 找到对应的 Channel
    //最终通过 Channel.writeAndFlush() 发送给客户端
    /*
     * 发布消息
     * 当有服务实例调用rTopic.publish(message)方法时，message对象会被序列化，并通过redis发布到指定的主题
     * 这里的sentDto参数是待发送的消息对象，会序列化后通过redis发布到指定主题
     * 使用场景：
        用户发送聊天消息时
        系统推送通知时
        任何需要跨服务实例传播的消息
     */
    public void sendMessage(MessageSendDto sendDto) {
        RTopic rTopic = redissonClient.getTopic(MESSAGE_TOPIC);//订阅指定Topic主题
        rTopic.publish(sendDto);//发布消息到redis然后广播集群；将序列化后的消息（MessageDto对象）在集群部署时，通过Redis发布到指定主题
    }

    /*
     * 订阅消息（服务器）
     * 消息接收器，这里的sentDto参数是从redis反序列化得到的消息对象
     */
    @PostConstruct//服务启动时自动执行，所以当前启动的服务实例会自动订阅Topic主题，并监听Topic主题
    public void lisMessage() {//所以这个方法是不需要手动调用的
        RTopic rTopic = redissonClient.getTopic(MESSAGE_TOPIC);//订阅指定Topic主题
        rTopic.addListener(MessageSendDto.class, (channel, sendDto) -> {//消息到达后，函数触发
            /*
             rTopic.addListener(MessageSendDto.class, (channel, message) -> {
            //MessageSendDto.class：监听的消息对象类型
            // channel: 消息来源的Redis频道名称（String类型），标识消息来源的Redis channel
            // message: 反序列化后的消息对象（MessageSendDto类型），实际收到的消息对象
            logger.info("收到消息: {}", message);
            });
             */
            logger.info("收到广播消息:{}", JSON.toJSONString(sendDto));
            channelContextUtils.sendMessage(sendDto);//接收到消息后，转发 消息给连接的客户端
        });
    }


}
